﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Ex05.MemoryGameLogic
{
    public delegate void CellOpenedEventHandler(CellOpenedEventArgs i_CellopenedEventArgs);

    public delegate void GameEndedEventHandler(GameEndedEventArgs i_GameEndedEventArgs);

    public delegate void TurnSwitchedEventHandler(CloseCellsEventArgs i_CloseCellsEventArgs);

    public delegate void ScoreChangedEventHandler(ScoreChangesEventArgs i_ScoreChangesEventArgs);

    public class Game
    {
        public event CellOpenedEventHandler CellOpened;

        public event GameEndedEventHandler GameEnded;
        
        public event TurnSwitchedEventHandler TurnSwitched;

        public event ScoreChangedEventHandler ScoreChanged;

        public enum eOpponentType
        {
            Human,
            Computer
        }

        private const bool k_FirstPlayerPlaying = true;

        private PlayerWithScore m_FirstPlayerWithScore;
        private PlayerWithScore m_SecondPlayerWithScore;
        private eOpponentType m_OpponentType = eOpponentType.Human;
        private eOpponentType m_CurrentPlayerType = eOpponentType.Human;
        private Player m_CurrentlyPlayingPlayer;
        private Board m_FullBoard;
        private Board m_PlayingBoard;
        private Board m_ComputerVisibleBoard;
        private BoardSize m_BoardSize;
        private bool m_FirstPlayerPlaying;
        private Cell m_FirstCell = null;
        private Cell m_SecondCell = null;

        public Game()
        {
            m_OpponentType = eOpponentType.Computer;
            m_FirstPlayerWithScore.Player = new Player();
            m_SecondPlayerWithScore.Player = new Player();
            m_CurrentlyPlayingPlayer = m_FirstPlayerWithScore.Player;
        }

        /// <summary>
        /// Gets or sets the name of the first player.
        /// </summary>
        public string FirstPlayerName
        {
            get { return m_FirstPlayerWithScore.Player.Name; }
            set { m_FirstPlayerWithScore.Player.Name = value; }
        }

        /// <summary>
        /// Gets or sets the name of the second player.
        /// </summary>
        public string SecondPlayerName
        {
            get { return m_SecondPlayerWithScore.Player.Name; }
            set { m_SecondPlayerWithScore.Player.Name = value; }
        }

        /// <summary>
        /// Gets or sets the opponent type.
        /// </summary>
        public eOpponentType OpponentType
        {
            get { return m_OpponentType; }
            set { m_OpponentType = value; }
        }

        /// <summary>
        /// Gets the size of the board.
        /// </summary>
        public BoardSize BoardSize
        {
            get { return m_BoardSize; }
            set { m_BoardSize = value; }
        }

        /// <summary>
        /// Returns a list of the allowed board sizes.
        /// </summary>
        /// <returns>A list of the allowed board sizes.</returns>
        public List<BoardSize> GetAllowedBoardSizes()
        {
            List<BoardSize> allowedBoardSizes = new List<BoardSize>();

            allowedBoardSizes.Add(new BoardSize(4, 4));
            allowedBoardSizes.Add(new BoardSize(4, 5));
            allowedBoardSizes.Add(new BoardSize(4, 6));
            allowedBoardSizes.Add(new BoardSize(5, 4));
            allowedBoardSizes.Add(new BoardSize(5, 6));
            allowedBoardSizes.Add(new BoardSize(6, 4));
            allowedBoardSizes.Add(new BoardSize(6, 5));
            allowedBoardSizes.Add(new BoardSize(6, 6));

            return allowedBoardSizes;
        }

        /// <summary>
        /// Initializes a single round.
        /// </summary>
        public void StartRound()
        {
            this.m_FullBoard = new Board(m_BoardSize);
            this.m_PlayingBoard = new Board(m_BoardSize);
            this.m_ComputerVisibleBoard = new Board(m_BoardSize);

            this.m_FullBoard.FillRandomPairCharacters();
            this.m_PlayingBoard.FillSpaces();

            this.m_FirstPlayerWithScore.Score = 0;
            this.m_SecondPlayerWithScore.Score = 0;
            
            this.m_FirstPlayerPlaying = k_FirstPlayerPlaying;
        }

        public PlayerWithScore FirstPlayerWithScore
        {
            get { return m_FirstPlayerWithScore; }
        }

        public PlayerWithScore SecondPlayerWithScore
        {
            get { return m_SecondPlayerWithScore; }
        }

        public bool IsGameEnded
        {
            get { return (m_FirstPlayerWithScore.Score * 2) + (m_SecondPlayerWithScore.Score * 2) == (m_PlayingBoard.NumberOfColumns * m_PlayingBoard.NumberOfRows); }
        }

        /// <summary>
        /// Returns the player that won, if the game has already ended.
        /// </summary>
        public Player Winner
        {
            get
            {
                Player retVal = null;

                if (IsGameEnded)
                {
                    retVal = m_FirstPlayerWithScore.Score > m_SecondPlayerWithScore.Score ?
                        m_FirstPlayerWithScore.Player : m_SecondPlayerWithScore.Player;
                }

                return retVal;
            }
        }

        /// <summary>
        /// Returns the playing board. This board contains only the visible cells.
        /// </summary>
        public Board PlayingBoard
        {
            get { return m_PlayingBoard; }
        }

        /// <summary>
        /// Returns the player that goes now.
        /// </summary>
        public Player CurrentlyPlayingPlayer
        {
            get { return m_CurrentlyPlayingPlayer; }
        }

        /// <summary>
        /// Returns the type of the currently playing player.
        /// </summary>
        public eOpponentType CurrentPlayerType
        {
            get { return m_CurrentPlayerType; }
        }

        /// <summary>
        /// Opens a cell on the board.
        /// </summary>
        /// <param name="i_Cell">A cell to open.</param>
        public void OpenCell(Cell i_Cell)
        {   
            OnCellOpened(i_Cell, m_FullBoard[i_Cell]);

            if (m_FirstCell == null)
            {
                m_FirstCell = i_Cell;
            }
            else if (m_SecondCell == null)
            {
                m_SecondCell = i_Cell;
                doNextTurn();
            }
        }

        private void doNextTurn()
        {
            if (m_FirstPlayerPlaying)
            {
                if (m_FullBoard[m_FirstCell] == m_FullBoard[m_SecondCell])
                {
                    m_FirstPlayerWithScore.Score++;
                    m_PlayingBoard[m_FirstCell] = m_FullBoard[m_FirstCell];
                    m_PlayingBoard[m_SecondCell] = m_FullBoard[m_SecondCell];

                    OnScoreChanged(m_FirstPlayerWithScore.Player, m_FirstPlayerWithScore.Score);
                }
                else
                {
                    m_FirstPlayerPlaying = !k_FirstPlayerPlaying;
                    m_CurrentlyPlayingPlayer = m_SecondPlayerWithScore.Player;
                    m_CurrentPlayerType = m_OpponentType;

                    OnTurnSwitched(m_FirstCell, m_SecondCell);
                }
            }
            else
            {
                if (m_FullBoard[m_FirstCell] == m_FullBoard[m_SecondCell])
                {
                    m_SecondPlayerWithScore.Score++;
                    m_PlayingBoard[m_FirstCell] = m_FullBoard[m_FirstCell];
                    m_PlayingBoard[m_SecondCell] = m_FullBoard[m_SecondCell];

                    OnScoreChanged(m_SecondPlayerWithScore.Player, m_SecondPlayerWithScore.Score);
                }
                else
                {
                    m_FirstPlayerPlaying = k_FirstPlayerPlaying;
                    m_CurrentlyPlayingPlayer = m_FirstPlayerWithScore.Player;
                    m_CurrentPlayerType = eOpponentType.Human;

                    OnTurnSwitched(m_FirstCell, m_SecondCell);
                }
            }

            m_FirstCell = null;
            m_SecondCell = null;

            if (IsGameEnded)
            {
                OnGameEnded(Winner);
            }
            else
            {
                if (m_OpponentType == eOpponentType.Computer && !m_FirstPlayerPlaying)
                {
                    computerMove();
                }
            }
        }

        private void computerMove()
        {
            bool finishedOpening = false;
            Cell firstCell = null;
            Cell secondCell = null;

            while (!finishedOpening && !IsGameEnded)
            {
                firstCell = generateAndGetComputerMove();
                m_PlayingBoard[firstCell] = m_FullBoard[firstCell];
                OnCellOpened(firstCell, m_FullBoard[firstCell]);
                secondCell = generateAndGetComputerMove();
                m_PlayingBoard[secondCell] = m_FullBoard[secondCell];
                OnCellOpened(secondCell, m_FullBoard[secondCell]);

                if (m_FullBoard[firstCell] == m_FullBoard[secondCell])
                {
                    m_SecondPlayerWithScore.Score++;
                    OnScoreChanged(m_SecondPlayerWithScore.Player, m_SecondPlayerWithScore.Score);
                }
                else
                {
                    finishedOpening = true;
                    m_PlayingBoard[firstCell] = ' ';
                    m_PlayingBoard[secondCell] = ' ';
                }
            }

            if (IsGameEnded)
            {
                OnGameEnded(Winner);
            }
            else
            {
                m_FirstPlayerPlaying = k_FirstPlayerPlaying;
                m_CurrentlyPlayingPlayer = m_FirstPlayerWithScore.Player;
                m_CurrentPlayerType = eOpponentType.Human;

                OnTurnSwitched(firstCell, secondCell);
            }
        }

        private Cell generateAndGetComputerMove()
        {
            List<Cell> emptyCellsList = this.m_PlayingBoard.GetEmptyCells();

            Random random = new Random();
            int randCell = random.Next(emptyCellsList.Count);

            return emptyCellsList[randCell];
        }

        protected virtual void OnCellOpened(Cell i_Cell, char i_Value)
        {
            if(CellOpened != null)
            {
                CellOpened.Invoke(new CellOpenedEventArgs(i_Cell, i_Value));
            }
        }

        protected virtual void OnScoreChanged(Player i_Player, int i_Score)
        {
            if (ScoreChanged != null)
            {
                ScoreChanged.Invoke(new ScoreChangesEventArgs(i_Player, i_Score));
            }
        }

        protected virtual void OnTurnSwitched(Cell i_FirstCell, Cell i_SecondCell)
        {
            if (TurnSwitched != null)
            {
                TurnSwitched.Invoke(new CloseCellsEventArgs(i_FirstCell, i_SecondCell));
            }
        }

        protected virtual void OnGameEnded(Player i_Winner)
        {
            if (GameEnded != null)
            {
                GameEnded.Invoke(new GameEndedEventArgs(i_Winner));
            }
        }
    }
}